package pie

import "fmt"

type Pipeline interface {
	AddFirst(handlers ...Handler) Pipeline

	AddLast(handlers ...Handler) Pipeline

	AddHandler(position int, handlers ...Handler) Pipeline

	IndexOf(func(Handler) bool) int

	LastIndexOf(func(Handler) bool) int

	Size() int

	Channel() Channel
}

type pipeline struct {
	head *handlerContext
	tail *handlerContext

	channel Channel
	size    int
}

func NewPipeline() Pipeline {

	p := &pipeline{}
	p.head = newHandlerContext(p, headHandler{}, nil, nil)
	p.tail = newHandlerContext(p, tailHandler{}, nil, nil)

	p.head.next = p.tail
	p.tail.prev = p.head

	// head + tail
	p.size = 2
	return p
}

func newHandlerContext(p Pipeline, handler Handler, prev, next *handlerContext) *handlerContext {
	hc := &handlerContext{
		pipeline: p,
		handler:  handler,
		prev:     prev,
		next:     next,
	}
	return hc
}

func (p *pipeline) addFirst(handler Handler) {
	oldNext := p.head.next
	p.head.next = newHandlerContext(p, handler, p.head, oldNext)
	oldNext.prev = p.head.next
	p.size++
}

func (p *pipeline) addLast(handler Handler) {

	oldPrev := p.tail.prev
	p.tail.prev = newHandlerContext(p, handler, oldPrev, p.tail)
	oldPrev.next = p.tail.prev
	p.size++
}

func (p *pipeline) AddFirst(handlers ...Handler) Pipeline {
	for _, h := range handlers {
		p.addFirst(h)
	}
	return p
}

func (p *pipeline) AddLast(handlers ...Handler) Pipeline {
	for _, h := range handlers {
		p.addLast(h)
	}
	return p
}

func (p *pipeline) AddHandler(position int, handlers ...Handler) Pipeline {

	if position >= p.size {
		panic(fmt.Errorf("invalid position: %d", position))
	}

	if -1 == position || position == p.size-1 {
		return p.AddLast(handlers...)
	}

	curNode := p.head
	for i := 0; i < position; i++ {
		curNode = curNode.next
	}

	for _, h := range handlers {
		oldNext := curNode.next
		curNode.next = newHandlerContext(p, h, curNode, oldNext)

		oldNext.prev = curNode.next
		curNode = curNode.next
		p.size++
	}
	return p
}

func (p *pipeline) IndexOf(comp func(Handler) bool) int {
	head := p.head

	for i := 0; ; i++ {
		if comp(head.handler) {
			return i
		}
		if head = head.next; head == nil {
			break
		}
	}

	return -1
}

func (p *pipeline) LastIndexOf(comp func(Handler) bool) int {
	tail := p.tail

	for i := p.size - 1; ; i-- {
		if comp(tail.handler) {
			return i
		}
		if tail = tail.prev; tail == nil {
			break
		}
	}

	return -1
}

func (p *pipeline) Size() int {
	return p.size
}

func (p *pipeline) Channel() Channel {
	return p.channel
}
